package com.wva.user.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.db.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wva.common.domain.dto.PageQueryDTO;
import com.wva.common.domain.vo.PageQueryVO;
import com.wva.common.domain.vo.UserProfileVO;
import com.wva.common.exception.BusinessException;
import com.wva.user.domain.po.User;
import com.wva.user.domain.po.UserFollow;
import com.wva.user.enums.RedisKey;
import com.wva.user.mapper.UserFollowMapper;
import com.wva.user.mapper.UserMapper;
import com.wva.user.service.UserFollowService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author hong
 * @since 2025-03-10
 */
@Service
public class UserFollowServiceImpl extends ServiceImpl<UserFollowMapper, UserFollow> implements UserFollowService {
    @Resource
    StringRedisTemplate stringRedisTemplate;
    @Resource
    UserMapper userMapper;

    @Override
    @Async
    public void follow(Long sourceUserId, Long targetUserId) {
        if (Objects.equals(sourceUserId, targetUserId)) {
            throw new BusinessException("不能自己关注自己哦");
        }

        //写入到缓存
        stringRedisTemplate.opsForZSet().add(RedisKey.FOLLOWER_SET.format(targetUserId), String.valueOf(sourceUserId), sourceUserId);
        stringRedisTemplate.opsForZSet().add(RedisKey.FOLLOWING_SET.format(sourceUserId), String.valueOf(targetUserId), targetUserId);

        //持久化到数据库
        UserFollow userFollow = new UserFollow();
        userFollow.setUserId(sourceUserId);
        userFollow.setFollowerId(targetUserId);
        save(userFollow);
    }

    @Override
    @Async
    public void cancelFollow(Long sourceUserId, Long targetUserId) {
        stringRedisTemplate.opsForZSet().remove(RedisKey.FOLLOWER_SET.format(targetUserId), String.valueOf(sourceUserId));
        stringRedisTemplate.opsForZSet().remove(RedisKey.FOLLOWING_SET.format(sourceUserId), String.valueOf(targetUserId));

        //持久化到数据库
        lambdaUpdate()
                .eq(UserFollow::getUserId, sourceUserId)
                .eq(UserFollow::getFollowerId, targetUserId)
                .remove();
    }

    @Override
    public Boolean isFollow(Long sourceUserId, Long targetUserId) {
        String key = RedisKey.FOLLOWER_SET.format(targetUserId);

        ;

        Boolean follow = stringRedisTemplate.opsForZSet().score(key, String.valueOf(sourceUserId)) != null;
        return Boolean.TRUE.equals(follow);
    }

    @Override
    public Map<Long, Boolean> isFollow(Long sourceUserId, List<Long> targetUserIds) {
        String key = RedisKey.FOLLOWING_SET.format(sourceUserId);

        //long类型的目标用户id集合转为字符串数组类型
        Object[] targetUserIdsToArray = targetUserIds.stream().map(String::valueOf).toArray();

        //返回Map表示是否在set内
        Map<Long, Boolean> result = new HashMap<>();
        List<Double> scores = stringRedisTemplate.opsForZSet().score(key, targetUserIdsToArray);
        if (CollectionUtil.isEmpty(scores)) return result;
        for (int i = 0; i < targetUserIds.size(); i++) {
            result.put(targetUserIds.get(i), scores.get(i) != null);
        }

        return result;
    }

    @Override
    public Long getFollowerCountByUserId(Long userId) {
        String key = RedisKey.FOLLOWER_SET.format(userId);
        Long count = stringRedisTemplate.opsForZSet().size(key);
        return Optional.ofNullable(count).orElse(0L);
    }

    @Override
    public PageQueryVO<UserProfileVO> listFollowers(PageQueryDTO pageQueryDTO, Long userId) {
        return listFollowerOrFollowing(
                RedisKey.FOLLOWER_SET.format(userId),
                pageQueryDTO,
                userId
        );
    }

    @Override
    public PageQueryVO<UserProfileVO> listFollowings(PageQueryDTO pageQueryDTO, Long userId) {
        return listFollowerOrFollowing(
                RedisKey.FOLLOWING_SET.format(userId),
                pageQueryDTO,
                userId
        );
    }

    /**
     * 分页查询某用户的粉丝或关注者
     * @param key redis集合的key
     * @param pageQueryDTO
     * @param userId
     * @return
     */
    private PageQueryVO<UserProfileVO> listFollowerOrFollowing(String key, PageQueryDTO pageQueryDTO, Long userId) {
        //redis zset分页查询
        Page page = new Page(pageQueryDTO.getPageNo(), pageQueryDTO.getPageSize());

        Integer pageNo = pageQueryDTO.getPageNo();
        Integer pageSize = pageQueryDTO.getPageSize();

        PageUtil.setFirstPageNo(1);
        long start = PageUtil.getStart(pageNo, pageSize);
        long end = PageUtil.getEnd(pageNo, pageSize);

        Set<String> followerUserIds = stringRedisTemplate.opsForZSet().range(key, start, end);

        //分页查询集合成员
        //没有关注者
        if (CollectionUtil.isEmpty(followerUserIds)) {
            return new PageQueryVO<>();
        }
        //有关注者, in查询走primary索引
        List<User> users = userMapper.selectBatchIds(followerUserIds);

        //我是否关注了该关注者
        List<Long> followerIds = users.stream().map(User::getId).collect(Collectors.toList());
        Map<Long, Boolean> isFollowFollower = isFollow(userId, followerIds);

        List<UserProfileVO> userProfileVOS = users.stream().map(user -> {
            UserProfileVO userProfileVO = BeanUtil.copyProperties(user, UserProfileVO.class);
            userProfileVO.setFollow(isFollowFollower.get(userProfileVO.getId()));
            return userProfileVO;
        }).collect(Collectors.toList());

        //返回
        PageQueryVO<UserProfileVO> vo = new PageQueryVO<>();

        //获取总成员数
        Long total = stringRedisTemplate.opsForZSet().size(key);

        //返回的分页信息处理
        if (total == null) total = 0L;
        vo.setPages(Math.round(Math.ceil(total*1.0 / pageQueryDTO.getPageSize())));
        vo.setTotal(total);
        vo.setList(userProfileVOS);

        return vo;
    }
}
